import os, sys
APP_ROOT = os.path.dirname(__file__)
sys.path.append(APP_ROOT)

import instance.app_instance

from functools import wraps

from settings import ProductionConfig
from settings import APP_USER_CONFIG
from utils.module_helper import *

from flask_cors import CORS
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask import session, Flask
from sqlalchemy import event
from sqlalchemy.orm import mapper
from flask_session import Session

from marshmallow_sqlalchemy import ModelConversionError, ModelSchema

def setup_schema(Base, session):
    # Create a function which incorporates the Base and session information
    def setup_schema_fn():
        for class_ in Base._decl_class_registry.values():
            if hasattr(class_, '__tablename__'):
                if class_.__name__.endswith('Schema'):
                    raise ModelConversionError(
                        "For safety, setup_schema can not be used when a"
                        "Model class ends with 'Schema'"
                    )

                class Meta(object):
                    model = class_
                    sqla_session = session

                schema_class_name = '%sSchema' % class_.__name__

                schema_class = type(
                    schema_class_name,
                    (ModelSchema,),
                    {'Meta': Meta}
                )

                setattr(class_, '__marshmallow__', schema_class)

    return setup_schema_fn

def create_app(config=ProductionConfig(), mininal=False, debug=True):
    app = Flask(__name__, static_folder='static', \
                static_url_path='/static', template_folder='blueprints')

    app.user_config = APP_USER_CONFIG
    app.config.from_object(config)

    app.session = Session(app)
    app.debug = debug

    app.db = SQLAlchemy(app)

    from sqlalchemy.ext.declarative import declared_attr
    class _Mixin(object):

        @declared_attr
        def __tablename__(cls):
            return cls.__name__.lower()

        id = app.db.Column(app.db.Integer, primary_key=True, autoincrement=True)
        create_t = app.db.Column(app.db.TIMESTAMP(True), name='create_t', nullable=False)

    '''add id adm create_t mixin'''
    app.db.Mixin = _Mixin

    app.ignored_models = set()
    def _ignore_model(model, *args, **kw):
        app.ignored_models.add(model.__name__)

    '''ignore none interceptable model'''
    app.ignore_model = _ignore_model

    def _record_operation_decorator(op_type, op_msg):
        '''
        :param type: "welcome"
        :param args:
        :param kw:
        :return:
        '''

        def decorater(fn):
            @wraps(fn)
            def wrapped(*args, **kwargs):
                from models.operation_model import OperationModel
                from sqlalchemy import func
                if 'user_id' in session:
                    app.db.session.add(OperationModel(create_t=func.now(), \
                                              user_id=session['user_id'], op_type=op_type, op_msg=op_msg))
                    app.db.session.commit()
                return fn(*args, **kwargs)
            return wrapped

        return decorater

    '''record operation decorator'''
    app.record = _record_operation_decorator

    app.migrate = Migrate(app, app.db)

    app.tasks = {}
    app.config.root_path = APP_ROOT
    app.login_namager = LoginManager()
    app.login_namager.init_app(app)
    app.login_manager.login_view = "auth.login"

    CORS(app, supports_credentials=True)

    event.listen(mapper, 'after_configured', setup_schema(app.db.Model, app.db.session))

    instance.app_instance.app = app

    unload_modules()

    load_module('models')

    if mininal is False:
        load_module('listeners')
        load_module('api')
        load_module('blueprints')
        load_module('tasks')

    app.db.create_all()

    @app.route('/favicon.ico')
    def favicon():
        return app.send_static_file('favicon.ico')

    return app